package javagamecontroller.components.joystick;

import javagamecontroller.connection.BluetoothMessenger;
import util.input.android.events.JoyStickEvent;
import com.javagamecontroller.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;


/**
 * This component creates an animation thread that draws a square joystick on
 * the screen. The joystick responds to a user dragging the "knob" of the image.
 * The joystick will automatically send data information via the bluetooth
 * messenger.
 * 
 * @author Ben Schwab
 * 
 */
public class JoyStick extends SurfaceView implements Runnable, Callback {

    private static final double DRAG_REGION_SIZE = .98;
    private static final double KNOB_SIZE = .6;
    private static final int FPS = 30;
    private static final int DELAY = 1000 / FPS;
    private int myKnobSize;
    private int myDragRegionSize;
    private int myPadSize;
    private Knob myKnob;
    private BitmapDrawable myPadImage;
    private Rect myPadBounds;
    private Point myCenter;
    private Vibrator myVibrator;
    private Thread myJoyStickThread = new Thread(this);
    private BluetoothMessenger myMessenger;
    private int myJoystickNumber = JoyStickEvent.LEFT;

    public JoyStick (Context context, AttributeSet attrs) {
        super(context, attrs);
        setZOrderOnTop(true);
        getHolder().setFormat(PixelFormat.TRANSPARENT);
        getHolder().addCallback(this);
        myPadImage =
                new BitmapDrawable(getResources(), BitmapFactory.decodeResource(getResources(),
                                                                                R.drawable.padarea));

        this.setOnTouchListener(myTouchListener);
        myMessenger = BluetoothMessenger.getInstance();
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.joystick);
        String s = a.getString(R.styleable.joystick_number);
        if ("left".equals(s)) {
            myJoystickNumber = JoyStickEvent.LEFT;
        }
        else if ("right".equals(s)) {
            myJoystickNumber = JoyStickEvent.RIGHT;
        }
        a.recycle();

    }

    @Override
    public void run () {
        long beforeTime;
        long timeDiff;
        long sleep;
        beforeTime = System.currentTimeMillis();
        while (true) {
            draw();
            timeDiff = System.currentTimeMillis() - beforeTime;
            sleep = DELAY - timeDiff;
            if (sleep < 0) {
                sleep = 2;
            }
            try {
                Thread.sleep(sleep);
            }
            catch (InterruptedException e) {

            }
            beforeTime = System.currentTimeMillis();
        }

    }

    /**
     * Draw the joystick to the canvas.
     */
    public void draw () {
        Canvas canvas = this.getHolder().lockCanvas();
        if (canvas == null) { return; }
        canvas.drawColor(0, PorterDuff.Mode.CLEAR);
        if (myPadImage != null && myPadBounds != null) {
            myPadImage.setBounds(myPadBounds);
            myPadImage.draw(canvas);
        }
        if (myKnob != null) {
            myKnob.draw(canvas);
        }

        this.getHolder().unlockCanvasAndPost(canvas);
    }

    /**
     * Determine the size of the joystick.
     * 
     * @param surfaceWidth width of surface
     * @param surfaceHeight height of surface
     */
    public void determineSize (int surfaceWidth, int surfaceHeight) {
        myPadSize = Math.min(surfaceWidth, surfaceHeight);
        myCenter = new Point(myPadSize / 2, myPadSize / 2);
        myDragRegionSize = (int) (DRAG_REGION_SIZE * myPadSize);
        myKnobSize = (int) (KNOB_SIZE * myDragRegionSize);
        myPadBounds =
                new Rect(getCenter().x - myDragRegionSize / 2,
                         getCenter().y - myDragRegionSize / 2,
                         getCenter().x + myDragRegionSize / 2, getCenter().y + myDragRegionSize / 2);
        myKnob = new Knob(getResources(), myCenter, myKnobSize, myDragRegionSize, this);
    }

    @Override
    public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) {
        myCenter = new Point(width / 2, height / 2);
        determineSize(width, height);

    }

    @Override
    public void surfaceCreated (SurfaceHolder holder) {
        myJoyStickThread.start();
        determineSize(this.getWidth(), this.getHeight());
    }

    @Override
    public void surfaceDestroyed (SurfaceHolder holder) {

    }

    /**
     * get the center point of this joystick layout.
     * 
     * @return
     */
    public Point getCenter () {
        return myCenter;
    }

    private final OnTouchListener myTouchListener = new OnTouchListener() {

        @Override
        public boolean onTouch (View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    myKnob.dragKnob(new Point((int) event.getX(), (int) event.getY()));
                    break;
                case MotionEvent.ACTION_DOWN:
                    myKnob.checkCapture(new Point((int) event.getX(), (int) event.getY()));
                    break;
                case MotionEvent.ACTION_UP:
                    myKnob.release();
                    break;
                default:
                    break;
            }

            JoyStickEvent myData = new JoyStickEvent(getAngle(), getMagnitude(), myJoystickNumber);
            myMessenger.write(myData);

            return true;

        }

        private double getMagnitude () {
            return myKnob.getMagnitude();

        }

        private double getAngle () {
            return myKnob.getAngle();
        }

    };

    /**
     * Add tacticle feedback for when the joystick hits the edge of its moveable
     * region.
     * 
     * @param vibrator the vibrator to add
     */
    public void addTacticleFeedback (Vibrator vibrator) {
        myVibrator = vibrator;
    }

    /**
     * Make the joystick cause a vibration event
     * 
     * @param time time of the vibration.
     */
    public void vibrate (int time) {
        if (myVibrator != null) {
            myVibrator.vibrate(time);
        }
    }

}
